import AsyncStorage from "@react-native-async-storage/async-storage";
import {
  IrohClient,
  IrohConfig,
  IrohNode,
  IrohDocument,
  IrohBlob,
  FileTransferProgress,
} from "./index";
import { IrohUtils } from "./utils";
import { IrohNativeClient, isNativeIrohAvailable } from "./native";

/**
 * React Native implementation of Iroh client
 *
 * This implementation automatically selects between:
 * 1. Native implementation (when available) - uses actual iroh-ffi bindings
 * 2. Fallback implementation - JavaScript-only for development/testing
 */
export class IrohClientImpl implements IrohClient {
  private client: IrohClient;

  constructor() {
    // Use native implementation if available, otherwise fall back to JS implementation
    if (isNativeIrohAvailable()) {
      console.log("Using native Iroh implementation");
      this.client = new IrohNativeClient();
    } else {
      console.log("Using fallback JavaScript Iroh implementation");
      this.client = new IrohFallbackClient();
    }
  }

  // Delegate all methods to the selected implementation
  async createNode(config?: IrohConfig): Promise<IrohNode> {
    return this.client.createNode(config);
  }

  async getNodeInfo(): Promise<IrohNode> {
    return this.client.getNodeInfo();
  }

  async shutdown(): Promise<void> {
    return this.client.shutdown();
  }

  async createDocument(name: string): Promise<IrohDocument> {
    return this.client.createDocument(name);
  }

  async getDocument(id: string): Promise<IrohDocument | null> {
    return this.client.getDocument(id);
  }

  async listDocuments(): Promise<IrohDocument[]> {
    return this.client.listDocuments();
  }

  async deleteDocument(id: string): Promise<boolean> {
    return this.client.deleteDocument(id);
  }

  async addBlob(data: Uint8Array): Promise<IrohBlob> {
    return this.client.addBlob(data);
  }

  async getBlob(hash: string): Promise<Uint8Array | null> {
    return this.client.getBlob(hash);
  }

  async addFile(filePath: string): Promise<IrohBlob> {
    return this.client.addFile(filePath);
  }

  async exportFile(hash: string, outputPath: string): Promise<boolean> {
    return this.client.exportFile(hash, outputPath);
  }

  async connect(nodeId: string): Promise<boolean> {
    return this.client.connect(nodeId);
  }

  async disconnect(nodeId: string): Promise<boolean> {
    return this.client.disconnect(nodeId);
  }

  async listConnections(): Promise<string[]> {
    return this.client.listConnections();
  }

  async shareDocument(documentId: string): Promise<string> {
    return this.client.shareDocument(documentId);
  }

  async receiveDocument(
    ticket: string,
    onProgress?: (progress: FileTransferProgress) => void,
  ): Promise<IrohDocument> {
    return this.client.receiveDocument(ticket, onProgress);
  }
}

/**
 * Fallback JavaScript-only implementation
 * Used when native modules are not available
 */
class IrohFallbackClient implements IrohClient {
  private nodeId: string;
  private isInitialized: boolean = false;
  private documents: Map<string, IrohDocument> = new Map();
  private blobs: Map<string, Uint8Array> = new Map();
  private connections: Set<string> = new Set();

  constructor() {
    this.nodeId = IrohUtils.generateNodeId();
  }

  async createNode(config?: IrohConfig): Promise<IrohNode> {
    try {
      // Initialize the node with provided config
      if (config?.nodeId) {
        this.nodeId = config.nodeId;
      }

      // Load persisted data
      await this.loadPersistedData();

      this.isInitialized = true;

      const node: IrohNode = {
        id: this.nodeId,
        isConnected: true,
        documents: Array.from(this.documents.values()),
      };

      // Persist node info
      await AsyncStorage.setItem("iroh_node_id", this.nodeId);

      return node;
    } catch (error) {
      throw new Error(`Failed to create node: ${error}`);
    }
  }

  async getNodeInfo(): Promise<IrohNode> {
    if (!this.isInitialized) {
      throw new Error("Node not initialized. Call createNode() first.");
    }

    return {
      id: this.nodeId,
      isConnected: this.isInitialized,
      documents: Array.from(this.documents.values()),
    };
  }

  async shutdown(): Promise<void> {
    await this.persistData();
    this.isInitialized = false;
    this.connections.clear();
  }

  async createDocument(name: string): Promise<IrohDocument> {
    if (!this.isInitialized) {
      throw new Error("Node not initialized");
    }

    const document: IrohDocument = {
      id: IrohUtils.generateDocumentId(),
      name,
      size: 0,
      created: new Date(),
      modified: new Date(),
    };

    this.documents.set(document.id, document);
    await this.persistData();

    return document;
  }

  async getDocument(id: string): Promise<IrohDocument | null> {
    return this.documents.get(id) || null;
  }

  async listDocuments(): Promise<IrohDocument[]> {
    return Array.from(this.documents.values());
  }

  async deleteDocument(id: string): Promise<boolean> {
    const deleted = this.documents.delete(id);
    if (deleted) {
      await this.persistData();
    }
    return deleted;
  }

  async addBlob(data: Uint8Array): Promise<IrohBlob> {
    const hash = await IrohUtils.calculateHash(data);
    this.blobs.set(hash, data);

    const blob: IrohBlob = {
      hash,
      size: data.length,
      format: "raw",
    };

    await this.persistData();
    return blob;
  }

  async getBlob(hash: string): Promise<Uint8Array | null> {
    return this.blobs.get(hash) || null;
  }

  async addFile(filePath: string): Promise<IrohBlob> {
    // In a real implementation, you would read the file using React Native's file system
    // For now, we'll simulate this with a placeholder
    const mockData = new TextEncoder().encode(`File content from ${filePath}`);
    return this.addBlob(mockData);
  }

  async exportFile(hash: string, outputPath: string): Promise<boolean> {
    const data = await this.getBlob(hash);
    if (!data) {
      return false;
    }

    // In a real implementation, you would write to the file system
    // For now, we'll just simulate success
    console.log(`Would export ${data.length} bytes to ${outputPath}`);
    return true;
  }

  async connect(nodeId: string): Promise<boolean> {
    // In a real implementation, this would establish a P2P connection
    this.connections.add(nodeId);
    console.log(`Connected to node: ${nodeId}`);
    return true;
  }

  async disconnect(nodeId: string): Promise<boolean> {
    const removed = this.connections.delete(nodeId);
    if (removed) {
      console.log(`Disconnected from node: ${nodeId}`);
    }
    return removed;
  }

  async listConnections(): Promise<string[]> {
    return Array.from(this.connections);
  }

  async shareDocument(documentId: string): Promise<string> {
    const document = await this.getDocument(documentId);
    if (!document) {
      throw new Error("Document not found");
    }

    // Generate a share ticket (in real implementation, this would be a proper iroh ticket)
    const ticket = IrohUtils.generateShareTicket(this.nodeId, documentId);
    return ticket;
  }

  async receiveDocument(
    ticket: string,
    onProgress?: (progress: FileTransferProgress) => void,
  ): Promise<IrohDocument> {
    // Parse the ticket to get source node and document info
    const ticketInfo = IrohUtils.parseShareTicket(ticket);

    // Simulate transfer progress
    if (onProgress) {
      for (let i = 0; i <= 100; i += 10) {
        onProgress({
          transferred: i,
          total: 100,
          percentage: i,
          status: i === 100 ? "completed" : "transferring",
        });
        // Small delay to simulate transfer
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
    }

    // Create the received document
    const document: IrohDocument = {
      id: IrohUtils.generateDocumentId(),
      name: `Received from ${ticketInfo.sourceNode}`,
      size: 1024, // Mock size
      created: new Date(),
      modified: new Date(),
    };

    this.documents.set(document.id, document);
    await this.persistData();

    return document;
  }

  private async loadPersistedData(): Promise<void> {
    try {
      const storedNodeId = await AsyncStorage.getItem("iroh_node_id");
      if (storedNodeId) {
        this.nodeId = storedNodeId;
      }

      const storedDocuments = await AsyncStorage.getItem("iroh_documents");
      if (storedDocuments) {
        const docs = JSON.parse(storedDocuments);
        this.documents = new Map(
          docs.map((doc: IrohDocument) => [
            doc.id,
            {
              ...doc,
              created: new Date(doc.created),
              modified: new Date(doc.modified),
            },
          ]),
        );
      }
    } catch (error) {
      console.warn("Failed to load persisted data:", error);
    }
  }

  private async persistData(): Promise<void> {
    try {
      await AsyncStorage.setItem("iroh_node_id", this.nodeId);

      const docsArray = Array.from(this.documents.values());
      await AsyncStorage.setItem("iroh_documents", JSON.stringify(docsArray));
    } catch (error) {
      console.warn("Failed to persist data:", error);
    }
  }
}

